import ffmpeg
import os
import uuid
import logging
import re
import subprocess
from typing import Dict, List, Optional, Tuple
import json

# 配置logger
logger = logging.getLogger(__name__)

class SubtitleTools:
    """字幕处理工具类"""
    
    def __init__(self):
        """初始化字幕工具"""
        self.supported_subtitle_formats = ['srt', 'ass', 'ssa', 'vtt']
    
    def _validate_file_exists(self, file_path: str, file_type: str) -> None:
        """
        验证文件是否存在
        
        Args:
            file_path: 文件路径
            file_type: 文件类型描述
        """
        if not os.path.exists(file_path):
            raise Exception(f"{file_type}文件不存在: {file_path}")
    
    def _validate_subtitle_format(self, subtitle_path: str) -> str:
        """
        验证字幕文件格式
        
        Args:
            subtitle_path: 字幕文件路径
            
        Returns:
            str: 字幕文件扩展名
        """
        subtitle_ext = os.path.splitext(subtitle_path)[1].lower()[1:]
        if subtitle_ext not in self.supported_subtitle_formats:
            raise ValueError(f"不支持的字幕格式: {subtitle_ext}")
        return subtitle_ext
    
    def _read_subtitle_content(self, subtitle_path: str) -> str:
        """
        读取字幕文件内容，支持多种编码格式
        
        Args:
            subtitle_path: 字幕文件路径
            
        Returns:
            str: 字幕文件内容
        """
        # 尝试多种编码格式
        encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb2312', 'big5', 'latin1']
        
        for encoding in encodings:
            try:
                with open(subtitle_path, 'r', encoding=encoding) as f:
                    subtitle_content = f.read()
                    print(f"字幕文件内容长度: {len(subtitle_content)} 字符")
                    print(f"字幕文件前100字符: {subtitle_content[:100]}")
                    print(f"字幕文件编码: {encoding}")
                    logger.info(f"字幕文件内容长度: {len(subtitle_content)} 字符")
                    logger.info(f"字幕文件前100字符: {subtitle_content[:100]}")
                    logger.info(f"字幕文件完整内容: {subtitle_content}")
                    logger.info(f"字幕文件编码: {encoding}")
                    return subtitle_content
            except UnicodeDecodeError:
                print(f"编码 {encoding} 失败，尝试下一个编码")
                logger.warning(f"编码 {encoding} 失败，尝试下一个编码")
                continue
            except Exception as e:
                print(f"读取字幕文件失败: {e}")
                logger.error(f"读取字幕文件失败: {e}")
                continue
        
        # 如果所有编码都失败，尝试二进制读取并猜测编码
        try:
            with open(subtitle_path, 'rb') as f:
                raw_content = f.read()
                
            # 尝试检测编码
            import chardet
            detected = chardet.detect(raw_content)
            detected_encoding = detected['encoding']
            confidence = detected['confidence']
            
            print(f"自动检测编码: {detected_encoding} (置信度: {confidence:.2f})")
            logger.info(f"自动检测编码: {detected_encoding} (置信度: {confidence:.2f})")
            
            if detected_encoding and confidence > 0.7:
                subtitle_content = raw_content.decode(detected_encoding)
                print(f"使用检测到的编码 {detected_encoding} 成功读取字幕文件")
                logger.info(f"使用检测到的编码 {detected_encoding} 成功读取字幕文件")
                return subtitle_content
            else:
                # 最后尝试使用latin1编码（通常不会失败）
                subtitle_content = raw_content.decode('latin1')
                print(f"使用latin1编码读取字幕文件（可能包含乱码）")
                logger.warning(f"使用latin1编码读取字幕文件（可能包含乱码）")
                return subtitle_content
                
        except Exception as e:
            print(f"所有编码尝试都失败: {e}")
            logger.error(f"所有编码尝试都失败: {e}")
            raise Exception(f"字幕文件读取失败，无法识别编码格式: {e}")
    
    def _escape_path_for_ffmpeg(self, path: str) -> str:
        """
        为FFmpeg转义路径中的特殊字符
        
        Args:
            path: 原始路径
            
        Returns:
            str: 转义后的路径
        """
        # 对于Linux系统，只需要转义冒号
        return path.replace(':', '\\:')
    
    def _convert_color_to_ass_format(self, color_hex: str) -> str:
        """
        将十六进制颜色转换为ASS格式
        
        Args:
            color_hex: 十六进制颜色 (0xRRGGBB)
            
        Returns:
            str: ASS格式颜色 (&HAABBGGRR)
        """
        # 移除0x前缀
        if color_hex.startswith('0x'):
            color_hex = color_hex[2:]
        
        # 确保是6位十六进制
        if len(color_hex) == 6:
            # 转换为ASS格式: &HAABBGGRR (AA=透明度, BB=蓝色, GG=绿色, RR=红色)
            # 默认透明度为00（不透明）
            # 注意：ASS格式中颜色顺序是BGR，不是RGB
            r = color_hex[0:2]
            g = color_hex[2:4]
            b = color_hex[4:6]
            ass_color = f"&H00{b}{g}{r}"  # 透明度=00, BGR顺序
            print(f"颜色转换: {color_hex} -> {ass_color}")
            logger.info(f"颜色转换: {color_hex} -> {ass_color}")
            return ass_color
        else:
            # 如果格式不正确，返回默认白色
            print(f"颜色格式错误: {color_hex}，使用默认白色")
            logger.warning(f"颜色格式错误: {color_hex}，使用默认白色")
            return "&H00FFFFFF"
    
    def _build_style_params(self, style_settings: Dict, include_position: bool = True) -> str:
        """
        构建字幕样式参数
        
        Args:
            style_settings: 样式设置
            include_position: 是否包含位置参数
            
        Returns:
            str: 样式参数字符串
        """
        style_params = []
        
        # 基本样式参数
        if style_settings.get('size'):
            # 调整字体大小：FFmpeg的字体大小通常需要除以2-3
            adjusted_size = max(12, style_settings['size'] // 2)  # 确保最小字体大小为12
            style_params.append(f"FontSize={adjusted_size}")
            print(f"字体大小调整: {style_settings['size']} -> {adjusted_size}")
            logger.info(f"字体大小调整: {style_settings['size']} -> {adjusted_size}")
        
        # 添加字体名称（确保字幕能够显示）
        style_params.append("FontName=Arial")
        
        # 颜色设置
        if style_settings.get('color_hex'):
            ass_color = self._convert_color_to_ass_format(style_settings['color_hex'])
            style_params.append(f"PrimaryColour={ass_color}")
        else:
            # 默认白色
            style_params.append("PrimaryColour=&H00FFFFFF")
        
        # 描边颜色
        if style_settings.get('stroke_color_hex'):
            ass_stroke_color = self._convert_color_to_ass_format(style_settings['stroke_color_hex'])
            style_params.append(f"OutlineColour={ass_stroke_color}")
        else:
            # 默认黑色描边
            style_params.append("OutlineColour=&H000000FF")
        
        # 描边宽度
        if style_settings.get('stroke_width'):
            # 调整描边宽度：通常需要除以2
            adjusted_stroke = max(1, style_settings['stroke_width'] // 2)
            style_params.append(f"Outline={adjusted_stroke}")
            print(f"描边宽度调整: {style_settings['stroke_width']} -> {adjusted_stroke}")
            logger.info(f"描边宽度调整: {style_settings['stroke_width']} -> {adjusted_stroke}")
        else:
            # 默认描边宽度
            style_params.append("Outline=2")
        
        # 阴影设置（确保字幕可见性）
        style_params.append("ShadowColour=&H000000FF")  # 黑色阴影
        style_params.append("Shadow=1")  # 阴影深度
        
        # 对齐方式（Alignment）- 关键参数
        # Alignment=8 表示底部居中，这是最常用的字幕位置
        style_params.append("Alignment=8")
        
        # 位置参数（可选）
        if include_position and style_settings.get('margin_v'):
            # 确保MarginV在合理范围内
            margin_v = int(style_settings['margin_v'])
            # 根据文档，MarginV应该考虑字体高度
            font_size = style_settings.get('size', 24) // 2  # 调整后的字体大小
            estimated_text_height = int(font_size * 1.2)  # 估算文本高度
            
            # 确保MarginV不会让字幕超出画面
            max_margin_v = 1080 - estimated_text_height - 20  # 假设1080p视频，留20px边距
            margin_v = min(margin_v, max_margin_v)
            margin_v = max(20, margin_v)  # 最小20px
            
            style_params.append(f"MarginV={margin_v}")
            print(f"MarginV设置: {margin_v} (字体大小: {font_size}, 估算文本高度: {estimated_text_height})")
            logger.info(f"MarginV设置: {margin_v} (字体大小: {font_size}, 估算文本高度: {estimated_text_height})")
        
        return ','.join(style_params)
    
    def _calculate_subtitle_position(self, position: Dict, video_width: int, video_height: int, font_size: int) -> int:
        """
        计算字幕位置（支持百分比，使用视频实际尺寸）
        
        Args:
            position: 位置信息（包含百分比）
            video_width: 视频实际宽度
            video_height: 视频实际高度
            font_size: 字体大小
            
        Returns:
            int: MarginV值
        """
        x = position.get('x', 0)
        y = position.get('y', 0)
        width = position.get('width', 200)
        height = position.get('height', 50)
        
        # 计算字幕区域的中心点Y坐标
        subtitle_center_y = y + height / 2
        
        # 计算字体高度
        adjusted_font_size = max(12, font_size // 2)  # 调整后的字体大小
        # FFmpeg的th（文本高度）通常比字体大小稍大，约为字体大小的1.4倍
        estimated_text_height = int(adjusted_font_size * 1.4)  # 更准确的文本高度估算
        
        # 计算字幕在指定区域内的垂直居中位置
        # 字幕应该显示在position定义的区域内，并且垂直居中
        # 字幕的底部位置 = 区域底部 - (区域高度 - 文本高度) / 2
        subtitle_bottom = y + height - (height - estimated_text_height) / 2
        
        # 计算字幕底部相对于视频高度的百分比
        bottom_percentage = subtitle_bottom / video_height
        
        # 将百分比转换为MarginV
        # 根据FFmpeg文档，当Alignment=8（底部居中）时，MarginV表示字幕离底部的距离
        margin_v_percentage = 1.0 - bottom_percentage
        
        # 计算实际的MarginV值（像素）
        margin_v = margin_v_percentage * video_height
        
        # 确保MarginV在合理范围内
        # MarginV + 文本高度 + 边距 <= 视频高度
        max_margin_v = video_height - estimated_text_height - 20  # 留20px边距
        margin_v = min(margin_v, max_margin_v)
        margin_v = max(20, margin_v)  # 最小20px
        
        logger.info(f"字幕位置计算: 画布坐标({x}, {y}), 区域尺寸({width}x{height}), 字幕中心Y({subtitle_center_y}), 字幕底部({subtitle_bottom}), 底部百分比({bottom_percentage:.2%}), MarginV百分比({margin_v_percentage:.2%}), 视频尺寸({video_width}x{video_height}), 估算文本高度({estimated_text_height}), MarginV={margin_v}")
        
        return int(margin_v)
    
    def _calculate_subtitle_position_from_percentage(self, position: Dict, video_info: Dict, font_size: int) -> int:
        """
        从百分比计算字幕位置（使用前端传递的视频信息）
        
        Args:
            position: 位置信息（包含百分比字段）
            video_info: 视频信息（包含尺寸）
            font_size: 字体大小
            
        Returns:
            int: MarginV值
        """
        # 获取视频尺寸（优先使用前端传递的，否则获取实际尺寸）
        video_width = video_info.get('width')
        video_height = video_info.get('height')
        
        if video_width is None or video_height is None:
            # 如果前端没有传递视频尺寸，则获取实际尺寸
            video_width, video_height = self._get_video_dimensions(video_info.get('path', ''))
            print(f"使用获取的视频尺寸: {video_width}x{video_height}")
            logger.info(f"使用获取的视频尺寸: {video_width}x{video_height}")
        else:
            print(f"使用前端传递的视频尺寸: {video_width}x{video_height}")
            logger.info(f"使用前端传递的视频尺寸: {video_width}x{video_height}")
        
        # 检查是否有直接的百分比设置
        y_percentage = position.get('y_percentage')
        if y_percentage is not None:
            # 直接使用前端传递的百分比
            # 根据FFmpeg文档，当Alignment=8（底部居中）时，MarginV表示字幕离底部的距离
            # 前端传递的y_percentage是从顶部开始的百分比，需要转换为从底部的距离
            margin_v_percentage = 1.0 - y_percentage
            
            # 计算实际的MarginV值（像素）
            margin_v = margin_v_percentage * video_height
            
            # 考虑字体高度，确保字幕不会超出画面
            adjusted_font_size = max(12, font_size // 2)  # 调整后的字体大小
            # FFmpeg的th（文本高度）通常比字体大小稍大，约为字体大小的1.4倍
            estimated_text_height = int(adjusted_font_size * 1.4)  # 更准确的文本高度估算
            
            # 确保MarginV在合理范围内
            # MarginV + 文本高度 + 边距 <= 视频高度
            max_margin_v = video_height - estimated_text_height - 20  # 留20px边距
            margin_v = min(margin_v, max_margin_v)
            margin_v = max(20, margin_v)  # 最小20px
            
            logger.info(f"直接百分比计算: Y百分比({y_percentage:.2%}), MarginV百分比({margin_v_percentage:.2%}), 视频尺寸({video_width}x{video_height}), 估算文本高度({estimated_text_height}), MarginV={margin_v}")
            return int(margin_v)
        else:
            # 使用原有的像素计算方式
            return self._calculate_subtitle_position(position, video_width, video_height, font_size)
    
    def _build_ffmpeg_subtitle_filter(self, subtitle_path: str, subtitle_ext: str, style_settings: Dict) -> str:
        """
        构建FFmpeg字幕滤镜
        
        Args:
            subtitle_path: 字幕文件路径
            subtitle_ext: 字幕文件扩展名
            style_settings: 样式设置
            
        Returns:
            str: FFmpeg滤镜字符串
        """
        # 当前FFmpeg版本不支持subtitles滤镜，直接使用drawtext滤镜
        print("当前FFmpeg版本不支持subtitles滤镜，使用drawtext滤镜")
        logger.info("当前FFmpeg版本不支持subtitles滤镜，使用drawtext滤镜")
        return self._build_drawtext_filter(subtitle_path, style_settings)
    
    def _execute_ffmpeg_command(self, cmd: List[str]) -> None:
        """
        执行FFmpeg命令
        
        Args:
            cmd: FFmpeg命令列表
        """
        try:
            print(f"执行FFmpeg命令: {' '.join(cmd)}")
            logger.info(f"执行FFmpeg命令: {' '.join(cmd)}")
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                encoding='utf-8',  # 明确指定UTF-8编码
                check=True
            )
            print(f"FFmpeg执行成功，输出: {result.stdout}")
            logger.info(f"FFmpeg执行成功，输出: {result.stdout}")
        except subprocess.CalledProcessError as e:
            stderr_output = e.stderr if e.stderr else "无错误输出"
            print(f"FFmpeg命令执行失败: {stderr_output}")
            print(f"FFmpeg返回码: {e.returncode}")
            logger.error(f"FFmpeg命令执行失败: {stderr_output}")
            logger.error(f"FFmpeg返回码: {e.returncode}")
            raise Exception(f"FFmpeg处理失败: {stderr_output}")
        except Exception as e:
            print(f"FFmpeg命令执行异常: {e}")
            logger.error(f"FFmpeg命令执行异常: {e}")
            raise Exception(f"FFmpeg处理异常: {e}")
    
    def _generate_output_path(self, output_dir: str, prefix: str, extension: str = 'mp4') -> str:
        """
        生成输出文件路径
        
        Args:
            output_dir: 输出目录
            prefix: 文件前缀
            extension: 文件扩展名
            
        Returns:
            str: 输出文件路径
        """
        os.makedirs(output_dir, exist_ok=True)
        output_filename = f"{prefix}_{uuid.uuid4().hex[:8]}.{extension}"
        return os.path.join(output_dir, output_filename)
    
    def _validate_output_file(self, output_path: str) -> None:
        """
        验证输出文件是否存在
        
        Args:
            output_path: 输出文件路径
        """
        if not os.path.exists(output_path):
            raise Exception(f"FFmpeg处理完成但输出文件不存在: {output_path}")
    
    def parse_srt_content(self, content: str) -> List[Dict]:
        """
        解析SRT字幕内容
        
        Args:
            content: SRT文件内容
            
        Returns:
            list: 字幕条目列表，每个条目包含index, start_time, end_time, text
        """
        entries = []
        blocks = content.strip().split('\n\n')
        
        for block in blocks:
            lines = block.strip().split('\n')
            if len(lines) >= 3:
                try:
                    index = int(lines[0])
                    time_line = lines[1]
                    text = '\n'.join(lines[2:])
                    
                    # 解析时间戳
                    time_match = re.match(r'(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})', time_line)
                    if time_match:
                        start_time = time_match.group(1)
                        end_time = time_match.group(2)
                        
                        entries.append({
                            'index': index,
                            'start_time': start_time,
                            'end_time': end_time,
                            'text': text
                        })
                except (ValueError, IndexError):
                    continue
        
        return entries
    
    def format_subtitle_text(self, text: str, max_chars_per_line: int = 20) -> List[str]:
        """
        格式化字幕文本，将长句子按停顿处分割
        
        Args:
            text: 原始字幕文本
            max_chars_per_line: 每行最大字符数
            
        Returns:
            list: 分割后的文本片段列表
        """
        # 定义停顿标点符号
        pause_chars = ['，', '。', '！', '？', '；', '：', ',', '.', '!', '?', ';', ':']
        
        # 如果文本长度小于等于最大字符数，直接返回
        if len(text) <= max_chars_per_line:
            return [text]
        
        # 按停顿符号分割文本
        segments = []
        current_segment = ""
        
        for char in text:
            current_segment += char
            
            # 如果遇到停顿符号且当前片段长度合理，进行分割
            if char in pause_chars and len(current_segment) >= max_chars_per_line * 0.6:
                segments.append(current_segment.strip())
                current_segment = ""
            # 如果当前片段超过最大长度，强制分割
            elif len(current_segment) >= max_chars_per_line:
                # 尝试在最后一个停顿符号处分割
                last_pause = -1
                for i, c in enumerate(current_segment):
                    if c in pause_chars:
                        last_pause = i
                
                if last_pause > max_chars_per_line * 0.5:
                    segments.append(current_segment[:last_pause + 1].strip())
                    current_segment = current_segment[last_pause + 1:]
                else:
                    # 没有合适的停顿位置，强制分割
                    segments.append(current_segment.strip())
                    current_segment = ""
        
        # 添加剩余文本
        if current_segment.strip():
            segments.append(current_segment.strip())
        
        return segments
    
    def calculate_split_timestamps(self, start_time: str, end_time: str, segments_count: int) -> List[Dict]:
        """
        计算分割后的时间戳
        
        Args:
            start_time: 开始时间
            end_time: 结束时间
            segments_count: 分割后的片段数量
            
        Returns:
            list: 时间戳列表
        """
        # 将时间戳转换为秒数
        def time_to_seconds(time_str: str) -> float:
            h, m, s = time_str.replace(',', '.').split(':')
            return int(h) * 3600 + int(m) * 60 + float(s)
        
        def seconds_to_time(seconds: float) -> str:
            h = int(seconds // 3600)
            m = int((seconds % 3600) // 60)
            s = seconds % 60
            return f"{h:02d}:{m:02d}:{s:06.3f}".replace('.', ',')
        
        start_seconds = time_to_seconds(start_time)
        end_seconds = time_to_seconds(end_time)
        duration = end_seconds - start_seconds
        
        # 平均分配时间
        segment_duration = duration / segments_count
        timestamps = []
        
        for i in range(segments_count):
            segment_start = start_seconds + i * segment_duration
            segment_end = start_seconds + (i + 1) * segment_duration
            timestamps.append({
                'start': seconds_to_time(segment_start),
                'end': seconds_to_time(segment_end)
            })
        
        return timestamps
    
    def format_srt_subtitle(self, subtitle_path: str, max_chars_per_line: int = 20) -> str:
        """
        格式化SRT字幕文件
        
        Args:
            subtitle_path: 字幕文件路径
            max_chars_per_line: 每行最大字符数
            
        Returns:
            str: 格式化后的字幕内容
        """
        try:
            with open(subtitle_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            entries = self.parse_srt_content(content)
            formatted_entries = []
            new_index = 1
            
            for entry in entries:
                # 格式化文本
                text_segments = self.format_subtitle_text(entry['text'], max_chars_per_line)
                
                if len(text_segments) == 1:
                    # 如果不需要分割，保持原样
                    formatted_entries.append({
                        'index': new_index,
                        'start_time': entry['start_time'],
                        'end_time': entry['end_time'],
                        'text': text_segments[0]
                    })
                    new_index += 1
                else:
                    # 需要分割，计算新的时间戳
                    timestamps = self.calculate_split_timestamps(entry['start_time'], entry['end_time'], len(text_segments))
                    
                    for i, (segment, timestamp) in enumerate(zip(text_segments, timestamps)):
                        formatted_entries.append({
                            'index': new_index,
                            'start_time': timestamp['start'],
                            'end_time': timestamp['end'],
                            'text': segment
                        })
                        new_index += 1
            
            # 生成格式化后的SRT内容
            formatted_content = ""
            for entry in formatted_entries:
                formatted_content += f"{entry['index']}\n"
                formatted_content += f"{entry['start_time']} --> {entry['end_time']}\n"
                formatted_content += f"{entry['text']}\n\n"
            
            return formatted_content.strip()
            
        except Exception as e:
            logger.error(f"格式化字幕失败: {e}")
            raise Exception(f"格式化字幕失败: {str(e)}")
    
    def add_subtitle(self, video_path: str, subtitle_path: str, output_dir: str) -> str:
        """
        为视频添加字幕
        
        Args:
            video_path: 视频文件路径
            subtitle_path: 字幕文件路径
            output_dir: 输出目录
            
        Returns:
            str: 输出视频文件路径
        """
        try:
            # 验证文件
            self._validate_file_exists(video_path, "视频")
            self._validate_file_exists(subtitle_path, "字幕")
            self._validate_subtitle_format(subtitle_path)
            
            # 读取字幕内容
            self._read_subtitle_content(subtitle_path)
            
            # 生成输出路径
            output_path = self._generate_output_path(output_dir, "video_with_subtitle")
            
            # 使用FFmpeg添加字幕
            stream = ffmpeg.input(video_path)
            subtitle_filter = f'subtitles={subtitle_path}'
            stream = ffmpeg.output(stream, output_path, vf=subtitle_filter)
            
            # 运行FFmpeg并捕获详细错误信息
            try:
                ffmpeg.run(stream, overwrite_output=True, quiet=True)
            except ffmpeg.Error as e:
                stderr_output = e.stderr.decode('utf-8') if e.stderr else "无错误输出"
                logger.error(f"FFmpeg添加字幕错误: {stderr_output}")
                raise Exception(f"FFmpeg添加字幕失败: {stderr_output}")
            
            # 验证输出文件
            self._validate_output_file(output_path)
            
            return output_path
        except Exception as e:
            raise Exception(f"添加字幕失败: {str(e)}")
    
    def extract_subtitle(self, video_path: str, output_dir: str, subtitle_format: str = 'srt') -> str:
        """
        从视频中提取字幕
        
        Args:
            video_path: 视频文件路径
            output_dir: 输出目录
            subtitle_format: 字幕格式
            
        Returns:
            str: 输出字幕文件路径
        """
        try:
            self._validate_file_exists(video_path, "视频")
            
            output_path = self._generate_output_path(output_dir, "extracted_subtitle", subtitle_format)
            
            # 使用FFmpeg提取字幕
            stream = ffmpeg.input(video_path)
            stream = ffmpeg.output(stream, output_path, vcodec='copy', acodec='copy', scodec='copy')
            ffmpeg.run(stream, overwrite_output=True, quiet=True)
            
            self._validate_output_file(output_path)
            return output_path
        except Exception as e:
            raise Exception(f"提取字幕失败: {str(e)}")
    
    def convert_subtitle_format(self, subtitle_path: str, target_format: str, output_dir: str) -> str:
        """
        转换字幕格式
        
        Args:
            subtitle_path: 字幕文件路径
            target_format: 目标格式
            output_dir: 输出目录
            
        Returns:
            str: 输出字幕文件路径
        """
        try:
            self._validate_file_exists(subtitle_path, "字幕")
            
            output_path = self._generate_output_path(output_dir, "converted_subtitle", target_format)
            
            # 使用FFmpeg转换字幕格式
            stream = ffmpeg.input(subtitle_path)
            stream = ffmpeg.output(stream, output_path, scodec=f'{target_format}')
            ffmpeg.run(stream, overwrite_output=True, quiet=True)
            
            self._validate_output_file(output_path)
            return output_path
        except Exception as e:
            raise Exception(f"转换字幕格式失败: {str(e)}")
    
    def burn_subtitle(self, video_path: str, subtitle_path: str, output_dir: str) -> str:
        """
        将字幕烧录到视频中（硬字幕）
        
        Args:
            video_path: 视频文件路径
            subtitle_path: 字幕文件路径
            output_dir: 输出目录
            
        Returns:
            str: 输出视频文件路径
        """
        try:
            self._validate_file_exists(video_path, "视频")
            self._validate_file_exists(subtitle_path, "字幕")
            
            output_path = self._generate_output_path(output_dir, "burned_subtitle")
            
            # 使用FFmpeg烧录字幕
            stream = ffmpeg.input(video_path)
            stream = ffmpeg.output(stream, output_path, vf=f'ass={subtitle_path}')
            ffmpeg.run(stream, overwrite_output=True, quiet=True)
            
            self._validate_output_file(output_path)
            return output_path
        except Exception as e:
            raise Exception(f"烧录字幕失败: {str(e)}")

    def apply_subtitle_with_settings(self, video_path: str, subtitle_path: str, subtitle_settings: Dict, output_dir: str) -> str:
        """
        根据字幕设置应用字幕到视频
        
        Args:
            video_path: 视频文件路径
            subtitle_path: 字幕文件路径
            subtitle_settings: 字幕设置信息（包含视频和canvas信息）
            output_dir: 输出目录
            
        Returns:
            str: 输出视频文件路径
        """
        try:
            # 验证文件
            self._validate_file_exists(video_path, "视频")
            self._validate_file_exists(subtitle_path, "字幕")
            subtitle_ext = self._validate_subtitle_format(subtitle_path)
            
            # 读取字幕内容
            self._read_subtitle_content(subtitle_path)
            
            # 获取字幕设置
            subtitle_style = subtitle_settings.get('subtitle_style', {})
            subtitle_positions = subtitle_settings.get('subtitle_positions', [])
            canvas_info = subtitle_settings.get('canvas', {})
            video_info = subtitle_settings.get('video', {})
            
            # 获取预览设置
            preview_duration = subtitle_settings.get('preview_duration')
            
            # 获取样式设置
            color = subtitle_style.get('color', '#ffffff')
            stroke_color = subtitle_style.get('strokeColor', '#000000')
            stroke_width = subtitle_style.get('strokeWidth', 2)
            size = subtitle_style.get('size', 24)
            opacity = subtitle_style.get('opacity', 100) / 100.0
            
            # 转换颜色格式（从#RRGGBB到0xRRGGBB）
            color_hex = color.replace('#', '0x')
            stroke_color_hex = stroke_color.replace('#', '0x')
            
            print(f"原始颜色设置: color={color}, stroke_color={stroke_color}")
            print(f"转换后颜色: color_hex={color_hex}, stroke_color_hex={stroke_color_hex}")
            logger.info(f"原始颜色设置: color={color}, stroke_color={stroke_color}")
            logger.info(f"转换后颜色: color_hex={color_hex}, stroke_color_hex={stroke_color_hex}")
            
            print(f"字幕文件路径: {subtitle_path}")
            print(f"字幕文件格式: {subtitle_ext}")
            print(f"字幕样式设置: {subtitle_style}")
            print(f"字幕位置设置: {subtitle_positions}")
            print(f"Canvas信息: {canvas_info}")
            print(f"视频信息: {video_info}")
            print(f"预览时长: {preview_duration}")
            
            logger.info(f"字幕文件路径: {subtitle_path}")
            logger.info(f"字幕文件格式: {subtitle_ext}")
            logger.info(f"字幕样式设置: {subtitle_style}")
            logger.info(f"字幕位置设置: {subtitle_positions}")
            logger.info(f"Canvas信息: {canvas_info}")
            logger.info(f"视频信息: {video_info}")
            logger.info(f"预览时长: {preview_duration}")
            
            # 构建基础FFmpeg命令
            cmd = [
                'ffmpeg',
                '-i', video_path
            ]
            
            # 如果设置了预览时长，限制视频时长
            if preview_duration:
                # 获取视频信息以确定实际预览时长
                from app.tools.video_tools import VideoTools
                video_tools = VideoTools()
                video_info_obj = video_tools.get_video_info(video_path)
                total_duration = video_info_obj.get('duration', 0)
                
                # 计算预览时长（不超过视频总时长）
                actual_preview_duration = min(preview_duration, total_duration)
                
                if actual_preview_duration > 0:
                    # 从视频中间部分开始
                    start_time = max(0, (total_duration - actual_preview_duration) / 2)
                    cmd.extend(['-ss', str(start_time), '-t', str(actual_preview_duration)])
                    print(f"设置预览时长: 开始时间={start_time}s, 时长={actual_preview_duration}s")
                    logger.info(f"设置预览时长: 开始时间={start_time}s, 时长={actual_preview_duration}s")
            
            cmd.append('-c:a')  # 复制音频流
            cmd.append('copy')
            
            # 准备样式设置
            style_settings = {
                'size': size,
                'color_hex': color_hex,
                'stroke_color_hex': stroke_color_hex,
                'stroke_width': stroke_width
            }
            
            # 处理字幕位置
            if subtitle_positions and len(subtitle_positions) > 0:
                # 使用第一个位置设置作为字幕位置
                position = subtitle_positions[0]
                
                # 使用前端传递的视频信息计算位置
                margin_v = self._calculate_subtitle_position_from_percentage(position, video_info, size)
                style_settings['margin_v'] = margin_v
                print(f"计算的字幕位置MarginV: {margin_v}")
                logger.info(f"计算的字幕位置MarginV: {margin_v}")
                
                # 使用drawtext滤镜
                drawtext_filter = self._build_drawtext_filter(subtitle_path, style_settings)
                print(f"使用drawtext滤镜: {drawtext_filter}")
                logger.info(f"使用drawtext滤镜: {drawtext_filter}")
                cmd.extend(['-vf', drawtext_filter])
            else:
                # 使用默认位置（底部）
                print("使用默认字幕位置（底部）")
                # 设置默认的MarginV值
                font_size = size // 2  # 调整后的字体大小
                estimated_text_height = int(font_size * 1.2)  # 估算文本高度
                default_margin_v = 50 + estimated_text_height  # 距离底部50px + 文本高度
                style_settings['margin_v'] = default_margin_v
                print(f"设置默认MarginV: {default_margin_v} (字体大小: {font_size}, 估算文本高度: {estimated_text_height})")
                logger.info(f"设置默认MarginV: {default_margin_v} (字体大小: {font_size}, 估算文本高度: {estimated_text_height})")
                
                # 使用drawtext滤镜
                drawtext_filter = self._build_drawtext_filter(subtitle_path, style_settings)
                print(f"默认位置drawtext滤镜: {drawtext_filter}")
                logger.info(f"默认位置drawtext滤镜: {drawtext_filter}")
                cmd.extend(['-vf', drawtext_filter])
            
            # 生成输出路径
            output_path = self._generate_output_path(output_dir, "video_with_subtitle")
            cmd.append(output_path)
            
            # 再次检查字幕文件是否存在
            print(f"检查字幕文件是否存在: {subtitle_path}")
            print(f"字幕文件存在: {os.path.exists(subtitle_path)}")
            if os.path.exists(subtitle_path):
                print(f"字幕文件大小: {os.path.getsize(subtitle_path)} 字节")
            
            # 执行FFmpeg命令
            self._execute_ffmpeg_command(cmd)
            
            # 验证输出文件
            self._validate_output_file(output_path)
            
            return output_path
            
        except Exception as e:
            raise Exception(f"应用字幕设置失败: {str(e)}")
    
    def _build_drawtext_filter(self, subtitle_path: str, style_settings: Dict) -> str:
        """
        构建drawtext滤镜作为备选方案，支持描边效果
        
        Args:
            subtitle_path: 字幕文件路径
            style_settings: 样式设置
            
        Returns:
            str: drawtext滤镜字符串
        """
        # 读取字幕文件内容
        with open(subtitle_path, 'r', encoding='utf-8') as f:
            content = f.read()
        
        # 解析字幕条目
        entries = self.parse_srt_content(content)
        if not entries:
            raise Exception("字幕文件为空或格式错误")
        
        # 构建多个drawtext滤镜（每个字幕条目一个）
        drawtext_filters = []
        
        for entry in entries:
            # 改进文本转义处理，确保中文字符正确显示
            text = entry['text']
            # 转义FFmpeg中的特殊字符
            text = text.replace("'", "\\'").replace('"', '\\"').replace(':', '\\:').replace('[', '\\[').replace(']', '\\]')
            # 确保文本是UTF-8编码
            if isinstance(text, str):
                text = text.encode('utf-8').decode('utf-8')
            
            start_time = entry['start_time']
            end_time = entry['end_time']
            
            # 构建drawtext滤镜
            font_size = style_settings.get('size', 24)
            # 调整字体大小：FFmpeg的字体大小通常需要除以2-3
            adjusted_font_size = max(12, font_size // 2)
            color = style_settings.get('color_hex', '0xFFFFFF').replace('0x', '')
            stroke_color = style_settings.get('stroke_color_hex', '0x000000').replace('0x', '')
            stroke_width = style_settings.get('stroke_width', 2)
            # 调整描边宽度：通常需要除以2
            adjusted_stroke_width = max(1, stroke_width // 2)
            
            # 计算位置
            # 检查是否有位置设置
            margin_v = style_settings.get('margin_v')
            if margin_v is not None:
                # 使用计算的位置
                y_expr = f"h-th-{margin_v}"
                print(f"使用计算的位置: y=h-th-{margin_v}")
            else:
                # 使用默认位置（距离底部30像素）
                y_expr = "h-th-30"
                print("使用默认位置: y=h-th-30")
            
            # 水平居中
            x_expr = "(w-tw)/2"
            
            # 获取字体路径
            font_path = self._get_font_path()
            
            # 构建描边效果（使用多个drawtext滤镜）
            stroke_filters = []
            
            # 只有当描边宽度大于0时才添加描边效果
            if adjusted_stroke_width > 0:
                # 描边滤镜（8个方向）
                stroke_offsets = [
                    (-adjusted_stroke_width, -adjusted_stroke_width),  # 左上
                    (0, -adjusted_stroke_width),                       # 上
                    (adjusted_stroke_width, -adjusted_stroke_width),   # 右上
                    (-adjusted_stroke_width, 0),                       # 左
                    (adjusted_stroke_width, 0),                        # 右
                    (-adjusted_stroke_width, adjusted_stroke_width),   # 左下
                    (0, adjusted_stroke_width),                        # 下
                    (adjusted_stroke_width, adjusted_stroke_width),    # 右下
                ]
                
                # 添加描边滤镜
                for offset_x, offset_y in stroke_offsets:
                    stroke_x_expr = f"({x_expr})+{offset_x}"
                    stroke_y_expr = f"({y_expr})+{offset_y}"
                    
                    if font_path:
                        stroke_filter = f"drawtext=text='{text}':fontsize={adjusted_font_size}:fontcolor=0x{stroke_color}:x={stroke_x_expr}:y={stroke_y_expr}:enable='between(t,{self._time_to_seconds_for_filter(start_time)},{self._time_to_seconds_for_filter(end_time)})':fontfile={font_path}"
                    else:
                        stroke_filter = f"drawtext=text='{text}':fontsize={adjusted_font_size}:fontcolor=0x{stroke_color}:x={stroke_x_expr}:y={stroke_y_expr}:enable='between(t,{self._time_to_seconds_for_filter(start_time)},{self._time_to_seconds_for_filter(end_time)})'"
                    
                    stroke_filters.append(stroke_filter)
            
            # 添加主文本滤镜（在描边之上）
            if font_path:
                main_filter = f"drawtext=text='{text}':fontsize={adjusted_font_size}:fontcolor=0x{color}:x={x_expr}:y={y_expr}:enable='between(t,{self._time_to_seconds_for_filter(start_time)},{self._time_to_seconds_for_filter(end_time)})':fontfile={font_path}"
            else:
                main_filter = f"drawtext=text='{text}':fontsize={adjusted_font_size}:fontcolor=0x{color}:x={x_expr}:y={y_expr}:enable='between(t,{self._time_to_seconds_for_filter(start_time)},{self._time_to_seconds_for_filter(end_time)})'"
            
            # 合并描边和主文本滤镜
            all_filters = stroke_filters + [main_filter]
            combined_entry_filter = ','.join(all_filters)
            
            print(f"字幕文本: {entry['text']}")
            print(f"转义后文本: {text}")
            print(f"字体大小: {font_size} -> {adjusted_font_size}")
            print(f"描边宽度: {stroke_width} -> {adjusted_stroke_width}")
            print(f"描边滤镜数量: {len(stroke_filters)}")
            logger.info(f"字幕文本: {entry['text']}")
            logger.info(f"转义后文本: {text}")
            logger.info(f"字体大小: {font_size} -> {adjusted_font_size}")
            logger.info(f"描边宽度: {stroke_width} -> {adjusted_stroke_width}")
            logger.info(f"描边滤镜数量: {len(stroke_filters)}")
            
            drawtext_filters.append(combined_entry_filter)
        
        # 合并所有字幕条目的滤镜
        combined_filter = ','.join(drawtext_filters)
        
        print(f"总Drawtext滤镜数量: {len(drawtext_filters)}")
        print(f"总滤镜: {combined_filter}")
        logger.info(f"总Drawtext滤镜数量: {len(drawtext_filters)}")
        logger.info(f"总滤镜: {combined_filter}")
        
        return combined_filter
    
    def _time_to_seconds_for_filter(self, time_str: str) -> float:
        """
        将SRT时间格式转换为秒数（用于FFmpeg滤镜）
        
        Args:
            time_str: 时间字符串 (HH:MM:SS,mmm)
            
        Returns:
            float: 秒数
        """
        try:
            time_parts = time_str.replace(',', '.').split(':')
            hours = int(time_parts[0])
            minutes = int(time_parts[1])
            seconds = float(time_parts[2])
            return hours * 3600 + minutes * 60 + seconds
        except Exception as e:
            logger.warning(f"时间格式转换失败: {time_str}, 错误: {e}")
            return 0.0 

    def _get_video_dimensions(self, video_path: str) -> Tuple[int, int]:
        """
        获取视频的实际尺寸
        
        Args:
            video_path: 视频文件路径
            
        Returns:
            Tuple[int, int]: (宽度, 高度)
        """
        try:
            # 使用ffprobe获取视频信息
            cmd = [
                'ffprobe',
                '-v', 'quiet',
                '-print_format', 'json',
                '-show_streams',
                video_path
            ]
            
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            data = json.loads(result.stdout)
            
            # 查找视频流
            for stream in data['streams']:
                if stream['codec_type'] == 'video':
                    width = int(stream['width'])
                    height = int(stream['height'])
                    print(f"视频实际尺寸: {width}x{height}")
                    logger.info(f"视频实际尺寸: {width}x{height}")
                    return width, height
            
            raise Exception("未找到视频流")
            
        except Exception as e:
            print(f"获取视频尺寸失败: {e}")
            logger.error(f"获取视频尺寸失败: {e}")
            # 返回默认尺寸
            return 1280, 720 

    def _get_font_path(self) -> str:
        """
        获取字体文件路径，优先使用系统可用的中文字体
        
        Returns:
            str: 字体文件路径
        """
        # 字体路径，按优先级排序（优先中文字体）
        font_paths = [
            # 中文字体（CJK字体）
            "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
            "/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc",
            "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc",
            "/usr/share/fonts/opentype/noto/NotoSerifCJK-Bold.ttc",
            # 系统默认字体路径
            "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
            "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
            "/usr/share/fonts/truetype/ubuntu/Ubuntu-Regular.ttf",
            # 其他可能的系统字体
            "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
            "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
        ]
        
        for font_path in font_paths:
            if os.path.exists(font_path):
                print(f"使用字体文件: {font_path}")
                logger.info(f"使用字体文件: {font_path}")
                return font_path
        
        # 如果都找不到，返回空字符串（让FFmpeg使用默认字体）
        print("未找到字体文件，使用FFmpeg默认字体")
        logger.warning("未找到字体文件，使用FFmpeg默认字体")
        return ""

 